// Copyright (C) 2024 Kumo inc.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#pragma once

#include <cassert>
#include <cstddef>
#include <memory>
#include <utility>

#include <deneb/internal/optional.h>
#include <deneb/statistics.h>

namespace deneb::internal {

    /// A mutable proxy interface to a statistics object.
    ///
    /// The `StatisticsMutator` allows modification of the members of a statistics
    /// object via a narrow interface, available only to internal classes. The point
    /// of this is that while we don't want the user to be able to modify the hit or
    /// miss count on a statistics object (it is "getter-only" in that sense), it's
    /// also not ideal, from an encapsulation standpoint, to make the cache classes
    /// (which do need to access and modify the hit and miss counts) friends of the
    /// statistics. This is especially true since the caches should only need to
    /// register hits or misses and not have to increment the count of total
    /// accesses. As such, we really require a "package-level" interface that is not
    /// visible to the end user, while at the same time providing an interface to
    /// internal classes. The `StatisticsMutator` is a proxy/adapter class that
    /// serves exactly this purpose. It is friends with the `Statistics` and can
    /// thus access its members. At the same time the interface it defines is narrow
    /// and provides only the necessary interface for the cache classes to register
    /// hits and misses.
    template<typename Key>
    class StatisticsMutator {
    public:
        using StatisticsPointer = std::shared_ptr<Statistics < Key>>;

        /// Constructor.
        StatisticsMutator() noexcept = default;

        /// Constructor.
        ///
        /// \param stats A shared pointer lvalue reference.
        StatisticsMutator(const StatisticsPointer &stats)  // NOLINT(runtime/explicit)
                : _stats(stats) {
        }

        /// Constructor.
        ///
        /// \param stats A shared pointer rvalue reference to move into the
        ///                   mutator.
        StatisticsMutator(StatisticsPointer &&stats)  // NOLINT(runtime/explicit)
                : _stats(std::move(stats)) {
        }

        /// Registers a hit for the given key with the internal statistics.
        ///
        /// \param key The key to register a hit for.
        void register_hit(const Key &key) {
            assert(has_stats());

            _stats->_total_accesses += 1;
            _stats->_total_hits += 1;

            auto iterator = _stats->_key_map.find(key);
            if (iterator != _stats->_key_map.end()) {
                iterator->second.hits += 1;
            }
        }

        /// Registers a miss for the given key with the internal statistics.
        ///
        /// \param key The key to register a miss for.
        void register_miss(const Key &key) {
            assert(has_stats());

            _stats->_total_accesses += 1;

            auto iterator = _stats->_key_map.find(key);
            if (iterator != _stats->_key_map.end()) {
                iterator->second.misses += 1;
            }
        }

        /// \returns A reference to the statistics object.
        Statistics <Key> &get() noexcept {
            assert(has_stats());
            return *_stats;
        }

        /// \returns A const reference to the statistics object.
        const Statistics <Key> &get() const noexcept {
            assert(has_stats());
            return *_stats;
        }

        /// \returns A `shared_ptr` to the statistics object.
        StatisticsPointer &shared() noexcept {
            return _stats;
        }

        /// \returns A const `shared_ptr` to the statistics object.
        const StatisticsPointer &shared() const noexcept {
            return _stats;
        }

        /// \returns True if the mutator has a statistics object, else false.
        bool has_stats() const noexcept {
            return _stats != nullptr;
        }

        /// \copydoc has_stats()
        explicit operator bool() const noexcept {
            return has_stats();
        }

        /// Resets the internal statistics pointer.
        void reset() {
            _stats.reset();
        }

    private:
        /// A shared pointer to a statistics object.
        std::shared_ptr<Statistics < Key>> _stats;
    };

}  // namespace deneb::internal
